//
// Created by Jinyu Zhu on 2022/4/17.
//

#ifndef PROVDB_TAG_GRAPH_GRAPH_H
#define PROVDB_TAG_GRAPH_GRAPH_H

#include <utility>

#include "string"
#include "vector"
#include "map"
#include "algorithm"
#include "outer.h"

#include "basic.h"


// pre definitions
class UndirectedGraph;
class UGNodeI;

typedef TPt<UndirectedGraph> PUndirectedGraph;

// Undirected (Single-) Graph
class UndirectedGraph {
public:
    class Node {
    private:
        std::vector<int> NIdVec;
        int Id;
        std::string Name;       // for log: "l:{filename}///{line_no}"; for tag: "t:{key}///{value}"
        double Value;
    public:
        Node() {}
        Node(const int &Id_, std::string Name_, const double& Value_) : NIdVec(), Id(Id_), Name(std::move(Name_)), Value(Value_) {};
        Node(const Node& N) = default;
        explicit Node(FIn &fIn);

    public:
        void Save(FOut &fOut) const;

    public:
        int GetDeg() const { return (int) NIdVec.size(); }

    private:
        void InsNIdVecSorted(const int &item) { NIdVec.insert(std::upper_bound(NIdVec.begin(), NIdVec.end(), item), item); }
        bool SearchBinSorted(const int &item) const { return std::binary_search(NIdVec.cbegin(), NIdVec.cend(), item); }
        int GetNbrNId(const int& Idx) { return NIdVec.at(Idx); }    // give index, return NbrNId
        int DelNbr(const int& NId); // give NbrNId, return NbrNId or -1

    public:
        friend class UndirectedGraph;
        friend class AlgorithmUndirectedGraph;
        friend class UGNodeI;
    };
private:
    TCRef CRef;
    int MaxNId, NEdges;
    // node main map
    std::map<int, Node> NodeMap;
    // name index
    std::map<std::string, int> NodeIndex;

    // small-constant maps
    std::vector<std::string> TagKeyVec;
    std::map<std::string, int> TagKeyMap;

private:
    void SaveNodeMap(FOut &fout) { save_map_basic(NodeMap, fout); }

    void LoadNodeMap(FIn &fin) { load_map_basic(NodeMap, fin); };

public:
    UndirectedGraph(): CRef(), MaxNId(0), NEdges(0), NodeMap(), NodeIndex(), TagKeyVec(), TagKeyMap() {}

    explicit UndirectedGraph(std::vector<std::string> &TagKeyVec_) : CRef(), MaxNId(0), NEdges(0),
                                                                     NodeMap(), NodeIndex(), TagKeyVec(TagKeyVec_),
                                                                     TagKeyMap() {
        for (auto i = 0; i < TagKeyVec.size(); i++) {
            TagKeyMap.insert({TagKeyVec.at(i), i});
        }
    }

    explicit UndirectedGraph(const std::string &LoadFilePath)
            : CRef(), MaxNId(0), NEdges(0), NodeMap(), NodeIndex(), TagKeyVec(), TagKeyMap() {
        FIn fin(LoadFilePath); fin.Load(MaxNId); fin.Load(NEdges); LoadNodeMap(fin); fin.Load(NodeIndex);
        fin.Load(TagKeyVec); fin.Load(TagKeyMap);
    }

    UndirectedGraph(const UndirectedGraph& Graph): CRef(), MaxNId(Graph.MaxNId), NEdges(Graph.NEdges),
                                                   NodeMap(Graph.NodeMap), NodeIndex(Graph.NodeIndex),
                                                   TagKeyVec(Graph.TagKeyVec), TagKeyMap(Graph.TagKeyMap) {};

// Use with smart pointer
public:
    static PUndirectedGraph New(std::vector<std::string> &TagKeyVec_) { return new UndirectedGraph(TagKeyVec_); }

    static PUndirectedGraph Load(const std::string &LoadFilePath) { return new UndirectedGraph(LoadFilePath); }

    PUndirectedGraph MakeCopy() { return new UndirectedGraph(*this); }

public:
    void Save(const std::string &SaveFilePath) {
        FOut fOut(SaveFilePath); fOut.Save(MaxNId); fOut.Save(NEdges);
        SaveNodeMap(fOut); fOut.Save(NodeIndex); fOut.Save(TagKeyVec); fOut.Save(TagKeyMap);
    }

private:
    int AddNode(const int &Id, const std::string &Name, const double& Value=0);

    int AddNode(const std::string &Name, const double& Value=0);

    std::string ParseLogNName(const std::string &fileName, const int &LineNo) {
        return "l:" + fileName + "///" + std::to_string(LineNo);
    }

    std::string ParseTagNName(const std::string &TagKey, const std::string &TagValue) {
        return "t:" + std::to_string(GetTagKeyId(TagKey)) + "///" + TagValue;
    }

    Node &GetNode(const int &Id) { return NodeMap.at(Id); }

    Node &GetNode(const std::string &Name) { return NodeMap.at(NodeIndex.at(Name)); }

    Node &GetLogNode(const std::string &LogFileName, const int &LineNo) {
        return GetNode(ParseLogNName(LogFileName, LineNo));
    }

    Node &GetTagNode(const std::string &TagKey, const std::string &TagValue) {
        return GetNode(ParseTagNName(TagKey, TagValue));
    }

public:
    UGNodeI BeginNodeI();
    UGNodeI EndNodeI();
    UGNodeI GetNodeNIdI(const int& NId);

    UndirectedGraph &operator=(const UndirectedGraph &Graph) {
        if (this != &Graph) {
            MaxNId = Graph.MaxNId; NEdges = Graph.NEdges;
            NodeMap = Graph.NodeMap; NodeIndex = Graph.NodeIndex;
            TagKeyVec = Graph.TagKeyVec; TagKeyMap = Graph.TagKeyMap;
        }
        return *this;
    }

    // return tag_key_id, -1 if not exists
    int GetTagKeyId(const std::string& TagKeyVal) {
        auto found = TagKeyMap.find(TagKeyVal);
        if (found == TagKeyMap.end()) return -1;
        else return (*found).second;
    }

    bool IsNode(const int &Id) { return NodeMap.find(Id) != NodeMap.end(); }

    bool IsEdge(const int &SrcId, const int &DstId);

    bool IsLogNode(const std::string &LogFileName, const int &LineNo) {
        return NodeIndex.find(ParseLogNName(LogFileName, LineNo)) != NodeIndex.end();
    }

    bool IsTagNode(const std::string &TagKey, const std::string &TagValue) {
        if (TagKeyMap.find(TagKey) == TagKeyMap.end()) return false;
        return NodeIndex.find(ParseTagNName(TagKey, TagValue)) != NodeIndex.end();
    }

    // return node id, -1 if existed
    int AddLogNode(const std::string &fileName, const int &LineNo, const double& Value=0);

    int AddTagNode(const std::string &TagKey, const std::string &TagValue, const double& Value=0);

    // return edge count if success; -1 if failed
    int AddEdge(const int &SrcNId, const int &DstNId);

    int AddEdge(const std::string &LogFileName, const int &LineNo,
                const std::string &TagKey, const std::string &TagValue);

    // return node id, -1 if not found
    int GetTagNodeId(const std::string &TagKey, const std::string &TagValue);
    int GetLogNodeId(const std::string &LogFileName, const int &LineNo);

    // return old node id if success, -1 if not found
    int DelNode(const int& NId);
    // return 1 if success, -1 if fail
    int DelEdge(const int &SrcNId, const int &DstNId);

    // return brief info
    std::string BriefInfo();

    friend class TPt<UndirectedGraph>;
    friend class AlgorithmUndirectedGraph;
    friend class UGNodeI;
};

// Iterators
class UGNodeI{
public:
    typedef std::map<int, UndirectedGraph::Node>::iterator Iter;

private:
    Iter iter;
    const UndirectedGraph* graph;

public:
    UGNodeI(): iter(), graph(nullptr) {}
    UGNodeI(const Iter& UGNodeIter, const UndirectedGraph* Graph): iter(UGNodeIter), graph(Graph) {}
    UGNodeI(const UGNodeI& NodeI) = default;

    UGNodeI& operator= (const UGNodeI& NodeI) { iter = NodeI.iter; graph = NodeI.graph; return *this; }
    bool operator == (const UGNodeI& NodeI) const { return iter == NodeI.iter; }
    bool operator != (const UGNodeI& NodeI) const { return iter != NodeI.iter; }

    // Increment iterator.
    UGNodeI& operator++ (int) { iter++; return *this; }
    void Next() { iter++; }

    // Getters and Setters
    int GetId() const { return (iter->second).Id; }
    std::string GetName() const { return (iter->second).Name; }
    double GetValue() const { return (iter->second).Value; }

    void SetName(const std::string& Name) { (iter->second).Name = Name; }
    void SetValue(const double& Value) { (iter->second).Value = Value; }

    int GetDeg() const { return (int) (iter->second).GetDeg(); }
    std::vector<int> GetNbrNodeIds() { return iter->second.NIdVec; }

};

#endif //PROVDB_TAG_GRAPH_GRAPH_H
